package com.code.monitor.handler;

import com.code.monitor.constants.ResourceType;
import com.code.monitor.model.MethodBean;
import com.code.monitor.resolve.MethodReslover;
import com.code.monitor.resolve.PageRenderReslover;
import com.code.monitor.resolve.UriMappingReslover;
import com.code.monitor.utils.Md5Utils;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.multipart.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author ggh
 * @date 2023/5/8
 */
@ChannelHandler.Sharable
public class HttpMessageHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
    private static final Logger logger = LoggerFactory.getLogger(HttpMessageHandler.class);
    private static Pattern pattern = Pattern.compile(".*\\.(js|css|png|jpg|map|ico|PNG|JPG|JPEG)");

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest req) throws Exception {
        try {
            logger.info("请求uri:{}", req.uri());
            String uri = req.uri();
            Map<String, Object> params = new HashMap<>();
            if (req.uri().contains("?")) {
                String[] requestParams = uri.substring(uri.indexOf("?") + 1).split("&");
                for (String requestParam : requestParams) {
                    String[] reqParams = requestParam.split("=");
                    params.put(reqParams[0], StringUtils.isEmpty(reqParams[1]) ? null : reqParams[1]);
                }
                uri = uri.substring(0, uri.indexOf("?"));
            }
            if (homePage(uri)) {
                byte[] bytes = PageRenderReslover.homePage().getBytes();
                ByteBuf body = ctx.alloc().buffer(bytes.length);
                body.writeBytes(bytes);
                ctx.writeAndFlush(success(body, ResourceType.HTML.getContentType(), false, null));
                return;
            }

            //如果是html页面
            if (isHtml(uri)) {
                byte[] bytes = PageRenderReslover.htmlPage(req.uri()).getBytes();
                ByteBuf body = ctx.alloc().buffer(bytes.length);
                body.writeBytes(bytes);
                ctx.writeAndFlush(success(body, ResourceType.HTML.getContentType(), false, null));
                return;
            }

            if (staticResource(uri)) {
                String contentType = contentType(req.uri());

                //如果是静态资源直接返回
                byte[] bytes = PageRenderReslover.staticResource(req.uri());
                String etag = Md5Utils.encryptBytes(bytes);
                ByteBuf body = ctx.alloc().buffer(bytes.length);
                body.writeBytes(bytes);
                ctx.writeAndFlush(success(body, contentType, true, etag));
                return;
            }
            //查找uri映射
            MethodBean methodBean = UriMappingReslover.getUriMapping(uri);
            if (methodBean != null) {
                Object obj = MethodReslover.invoke(methodBean, buildRequest(req, params));
                byte[] bytes = ((String) obj).getBytes(Charset.defaultCharset());
                ByteBuf body = ctx.alloc().buffer(bytes.length);
                body.writeBytes(bytes);
                ctx.writeAndFlush(success(body, HttpHeaderValues.APPLICATION_JSON.toString(), false, null));
                return;
            }

            //不存在的uri
            logger.error("不存在的uri：{}", req.uri());
            byte[] bytes = new byte[0];
            ByteBuf body = ctx.alloc().buffer(bytes.length);
            body.writeBytes(bytes);
            ctx.writeAndFlush(notFound(body, HttpHeaderValues.TEXT_PLAIN.toString()));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    private FullHttpResponse success(ByteBuf body, String contentType, boolean staticResource, String etag) {
        DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, body);
        if (staticResource) {
            buildStaticResourceResponseHeader(response, contentType, body.readableBytes(), etag);
        } else {
            buildResponseHeader(response, contentType, body.readableBytes());
        }

        return response;
    }

    private FullHttpResponse notFound(ByteBuf body, String contentType) {
        DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND, body);
        buildResponseHeader(response, contentType, body.readableBytes());

        return response;
    }

    private void buildResponseHeader(FullHttpResponse response, String contentType, int bodyLen) {
        response.headers().add(HttpHeaderNames.CACHE_CONTROL, HttpHeaderValues.NO_CACHE);
        response.headers().add(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
        response.headers().add(HttpHeaderNames.CONTENT_TYPE, contentType);
        response.headers().add(HttpHeaderNames.CONTENT_LENGTH, bodyLen);

        response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
        response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_HEADERS, "Origin, X-Requested-With, Content-Type, Accept");
        response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_METHODS, "GET, POST, PUT,DELETE");
        response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
    }

    private void buildStaticResourceResponseHeader(FullHttpResponse response, String contentType, int bodyLen, String etag) {
        response.headers().add(HttpHeaderNames.CONTENT_TYPE, contentType);
        response.headers().add(HttpHeaderNames.CONTENT_LENGTH, bodyLen);

        response.headers().set(HttpHeaderNames.ACCEPT_RANGES, "bytes");
        response.headers().set(HttpHeaderNames.CACHE_CONTROL, "max-age=315360000");
        response.headers().set(HttpHeaderNames.ETAG, etag);
        response.headers().set(HttpHeaderNames.DATE, new Date());
    }

    private Object[] buildRequest(FullHttpRequest request, Map<String, Object> params) throws IOException {
        String contentType = request.headers().get(HttpHeaderNames.CONTENT_TYPE);
        if (StringUtils.isEmpty(contentType)) {
            return new Object[]{params};
        }
        if (contentType.contains(HttpHeaderValues.APPLICATION_JSON.toString())) {
            String body = request.content().toString(Charset.defaultCharset());
            return new Object[]{body};
        } else if (contentType.contains(HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.toString())) {
            String body = request.content().toString(Charset.defaultCharset());
            String[] requestParams = body.split("&");
            Map<String, Object> reqParams = new HashMap<>();
            for (String requestParam : requestParams) {
                String[] reqs = requestParam.split("=");
                reqParams.put(reqs[0], (reqs.length == 1 || StringUtils.isEmpty(reqs[1])) ? null : reqs[1]);
            }
            return new Object[]{reqParams};
        } else if (contentType.contains(HttpHeaderValues.MULTIPART_FORM_DATA.toString())) {
            HttpDataFactory factory = new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE);
            HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(factory, request);
            List<InterfaceHttpData> dataList = decoder.getBodyHttpDatas();

            Map<String, Object> reqParams = new HashMap<>();
            for (InterfaceHttpData data : dataList) {
                if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.FileUpload) {
                    FileUpload fileUpload = (FileUpload) data;
                    if (fileUpload.isCompleted()) {
                        reqParams.put(fileUpload.getName(), fileUpload);
                    }
                } else {
                    Attribute attribute = (Attribute) data;
                    reqParams.put(attribute.getName(), attribute.getValue());
                }
            }

            return new Object[]{reqParams};
        }
        return new Object[]{params};
    }

    private boolean homePage(String uri) {
        return "/".equals(uri) || "/index".equals(uri);
    }

    private boolean staticResource(String uri) {
        return pattern.matcher(uri).matches();
    }

    private boolean isHtml(String uri) {
        return uri.endsWith(".html");
    }

    private static String contentType(String uri) {
        Matcher matcher = pattern.matcher(uri);
        while (matcher.matches()) {
            return ResourceType.contentType(matcher.group(1));
        }
        return null;
    }

    public static void main(String[] args) {
        System.out.println(pattern.matcher("aa.jpg").matches());
        System.out.println(pattern.matcher("ght.css").matches());
        System.out.println(pattern.matcher("aa/ag//mmp.js").matches());
        System.out.println(pattern.matcher("aa/tbb/").matches());
        System.out.println(pattern.matcher("ga/r").matches());

        System.out.println(contentType("/aaa/aaa.jpg"));
        System.out.println(contentType("/aaa/aaa.js"));
        System.out.println(contentType("/aaa/aaa.css"));
        System.out.println(contentType("/aaa/aaa.png"));
    }
}
